/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pedigreereconstruction;

import distribution.RelationshipDistribution;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import relationships.Relationship;
import java.util.Iterator;
import java.util.List;
import org.jgrapht.*;
import org.jgrapht.graph.*;
import org.jgrapht.traverse.DepthFirstIterator;
import pedigreereconstruction.Individual;
import relationships.ProbabilisticRelationship;
import org.jgrapht.alg.DijkstraShortestPath;
import relationships.RelationshipTypes;

/**
 *
 * @author arthiramachandran
 */
public abstract class Pedigree {

    private DirectedGraph<Individual, Relationship> graph;

    static double getThreshold() {
        return (10 ^ -10);
    }

    static double getLogThreshold() {
        return (Double.NEGATIVE_INFINITY);
    }

    public Pedigree() {
        graph = new DefaultDirectedGraph<Individual, Relationship>(
                Relationship.class);
    }

    /**
     * 1) Check if individuals exist
     *  if not, create individuals/nodes
     * 2) Add edge between individuals
     * 
     * @param r
     * @param symmetric  
     * 
     */
    // TODO: fix the inheritance so pedigree doesn't use any probabilistic things
    public void addRelationship(ProbabilisticRelationship r, boolean symmetric) {
        if (!graph.containsVertex(r.individual1)) {
            graph.addVertex(r.individual1);
        }
        if (!graph.containsVertex(r.individual2)) {
            graph.addVertex(r.individual2);
        }
        boolean result = graph.addEdge(r.individual1, r.individual2, r);
        //System.out.println(r + "\t" + result); // need to be able to add multiples
        // TODO: might need to adjust probabilities to account for 2 edges instead of 1
        if (symmetric) {
            ProbabilisticRelationship r2;
            try {
                r2 =
                        new ProbabilisticRelationship(
                        r.individual2, r.individual1,
                        r.totalSharedIBD, r.numberOfSharedSegments, r.relationshipType);
                r2.setRelationshipDistribution(r.getRelationshipDistribution());
                r2.calculateRelationshipLikelihood();
                graph.addEdge(r.individual2, r.individual1, r2);
            } catch (Exception ex) {
                Logger.getLogger(Pedigree.class.getName()).log(Level.SEVERE,
                        null, ex);
            }
        }
    }

    public String printGraph() {
        //Print out the graph to be sure it's really complete
        //System.out.println("Number of edges: " + graph.edgeSet().size());
        //System.out.println("Number of vertices: " + graph.vertexSet().size());
        String s = "";

        DepthFirstIterator<Individual, Relationship> iter =
                new DepthFirstIterator<Individual, Relationship>(graph);
        Individual i;
        while (iter.hasNext()) {
            i = iter.next();
            s += "Vertex " + i.toString() + ": "
                    + graph.outgoingEdgesOf(i).toString() + "\n";
        }

        return s;
    }

    public void removeRelationship(Relationship r) {
        graph.removeEdge(r);
        if (graph.outDegreeOf(r.individual1) == 0 && graph.inDegreeOf(
                r.individual1) == 0) {
            graph.removeVertex(r.individual1);
        }
        if (graph.outDegreeOf(r.individual2) == 0 && graph.inDegreeOf(
                r.individual2) == 0) {
            graph.removeVertex(r.individual2);
        }
    }

    public void removeIndividual(Individual i) {
        if (graph.outDegreeOf(i) == 0 && graph.inDegreeOf(i) == 0) {
            graph.removeVertex(i);
        }
    }

    Pedigree split(double percentageToKeep) {
        return null;
    }

    Relationship getRelationship(Individual i1, Individual i2) {
        return this.graph.getEdge(i1, i2);
    }

    Iterator<Relationship> getRelationships(Individual i) {
        if (graph.containsVertex(i)) {
            return this.graph.edgesOf(i).iterator();
        } else {
            return null;
        }
    }

    Iterator<Relationship> getIncomingRelationships(Individual i) throws
            Exception {
        if (!this.graph.containsVertex(i)) {
            System.out.println("&&&&&Individual " + i + " not present in pedigree " + this.printGraph());
            throw new Exception("Individual " + i + " not present in pedigree " + this.printGraph());
        }
        return this.graph.incomingEdgesOf(i).iterator();
    }

    Iterator<Relationship> getOutgoingRelationships(Individual i) {

        if (this.graph.containsVertex(i) && this.graph.outDegreeOf(i) != 0) {
            return this.graph.outgoingEdgesOf(i).iterator();
        } else {
            return Collections.<Relationship>emptyList().iterator();
        }
    }

    Iterator<Relationship> getRelationshipIterator() {
        return graph.edgeSet().iterator();
    }

    /*
    Iterator<Individual> getIndividualsIterator() {
    return null;
    }*/
    /**
     * 
     * @param p2
     * @return 
     *  true,  if there is at least one individual present in both pedigrees 
     *  false, otherwise
     */
    boolean hasCommonIndividuals(Pedigree p2) {
        DepthFirstIterator<Individual, Relationship> iter1 =
                new DepthFirstIterator<Individual, Relationship>(this.graph);
        while (iter1.hasNext()) {
            Individual i1;
            i1 = iter1.next();
            DepthFirstIterator<Individual, Relationship> iter2 =
                    new DepthFirstIterator<Individual, Relationship>(p2.graph);
            while (iter2.hasNext()) {
                Individual i2;
                i2 = iter2.next();
                if (i1.equals(i2)) {
                    return true;
                }
            }
        }

        return false;
    }

    String founder() {
        // doesnt actually get teh founder - need to think of a decent way to do that.
        String s = "";
        for (Individual i : graph.vertexSet()) {
            s += i.id + ",";
        }
        return s;
        //throw new UnsupportedOperationException("Not yet implemented");
    }

    boolean containsRelationship(Relationship r) {
        for (Relationship r2 : graph.edgeSet()) {
            if (r2.equals(r)) {
                //System.out.println(r + "\t" + r2);
                return true;
            }
        }
        return false;
        //return graph.containsEdge(r);
    }

    boolean containsRelationship(String id1, String id2, RelationshipTypes rt) {
        Individual i1 = new Individual(id1);
        Individual i2 = new Individual(id2);

        for (Relationship r : this.getRelationships()) {
            if (r.containsIndividual(i1) && r.containsIndividual(i2) && r.relationshipType.equals(rt)) {
                System.out.println(r);
                return true;
            }
        }
        return false;
        //return graph.containsEdge(r);
    }

    boolean containsRelationshipWithMissingParent(Relationship r) {
        for (Relationship r2 : graph.edgeSet()) {

            // specific case where the parent is missing in one of the relationships 
            // - maybe one relationship can be used to fill in the blanks
            if (r.relationshipType.equals(PedigreeReconstruction.parent_child)
                    && r2.relationshipType.equals(PedigreeReconstruction.parent_child)) {
                if (r2.individual1.isMissing() || r.individual1.isMissing()) {
                    /*if (r2.individual2.equals(r.individual2)) {
                    System.out.println(r + " " + r2);
                    }
                     * 
                     */
                    return r2.individual2.equals(r.individual2);
                }
            }
        }
        return false;
        //return graph.containsEdge(r);
    }

    int getNumberOfRelationships() {
        return graph.edgeSet().size();
    }

    int getNumberOfIndividuals() {
        return graph.vertexSet().size();
    }

    private ArrayList<Individual> getNeighbours(Individual i) {
        ArrayList<Individual> individuals = new ArrayList<Individual>();

        for (Individual potentialNeighbor : graph.vertexSet()) {
            if (graph.containsEdge(potentialNeighbor, i)) {
                individuals.add(potentialNeighbor);
            }
            if (graph.containsEdge(i, potentialNeighbor)) {
                individuals.add(potentialNeighbor);
            }
        }

        return individuals;
    }

    ArrayList<Pedigree> getSubPedigreesRecursive(Individual i, int D,
            HashMap<Individual, Boolean> processing) {
        ArrayList<Pedigree> listOfPedigrees = new ArrayList<Pedigree>();
        if (D == 1) {
            Pedigree p = new ProbabilisticPedigree();
            p.addIndividual(i);
            listOfPedigrees.add(p);
        } else {
            for (Individual child : this.getNeighbours(i)) {
                if (processing.get(child)) {
                    continue;
                }
                processing.put(child, true);
                for (Pedigree p : getSubPedigreesRecursive(child, D - 1,
                        processing)) {
                    p.addIndividual(child);
                    listOfPedigrees.add(p);
                }
            }
        }

        processing.put(i, false);
        return listOfPedigrees;
    }

    ArrayList<Pedigree> getSubPedigrees(Individual i, int D) {
        HashMap<Individual, Boolean> processing =
                new HashMap<Individual, Boolean>();
        for (Individual ind : this.getIndividuals()) {
            processing.put(ind, false);
        }
        processing.put(i, true);
        ArrayList<Pedigree> listOfPedigrees = getSubPedigreesRecursive(i, D,
                processing);
        // fill in the edges
        for (Pedigree p : listOfPedigrees) {
            System.out.println("Beg p:" + p.printGraph());

            p.addIndividual(i);
            for (Relationship r : this.graph.edgeSet()) {
                if (p.containsIndividual(r.individual1)
                        && p.containsIndividual(r.individual2)) {
                    ProbabilisticRelationship r2;
                    try {
                        r2 = new ProbabilisticRelationship(r);
                        p.addRelationship(r2, false);
                    } catch (Exception ex) {
                        Logger.getLogger(Pedigree.class.getName()).log(
                                Level.SEVERE, null, ex);
                    }
                }
            }
            System.out.println("End p:" + p.printGraph());
        }


        return listOfPedigrees; // at the end, need to populate pedigrees with vertices
    }

    private void addIndividual(Individual i) {
        this.graph.addVertex(i);
    }

    public Iterable<Individual> getIndividuals() {
        return this.graph.vertexSet();
    }

    public Iterable<Individual> getMissingIndividuals() {
        ArrayList<Individual> missing = new ArrayList<Individual>();
        for (Individual i : this.getIndividuals()) {
            if (i.isMissing()) {
                missing.add(i);
            }
        }
        return missing;
    }

    public ArrayList<Relationship> getNonMissingRelationships() {
        ArrayList<Relationship> nonmissing = new ArrayList<Relationship>();
        for (Relationship r : this.getRelationships()) {
            if (!r.containsMissing()) {
                nonmissing.add(r);
            }
        }
        return nonmissing;
    }

    public Iterable<Relationship> getRelationships() {
        return this.graph.edgeSet();
    }

    public Iterator<Individual> getIndividualsIterator() {
        return this.graph.vertexSet().iterator();
    }

    public boolean containsIndividual(Individual i) {
        for (Individual i2 : this.graph.vertexSet()) {
            if (i.equals(i2)) {
                return true;
            }
        }
        return false;
        //return this.graph.vertexSet().contains(i);
    }

    public Individual getIndividual(String string) {
        Individual i2 = new Individual(string);
        for (Individual i : graph.vertexSet()) {
            if (i.equals(i2)) {
                return i;
            }
        }
        return null;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Pedigree) {
            Pedigree p = (Pedigree) obj;
            /*System.out.println("Vertex: " + this.graph.vertexSet().equals(
            p.graph.vertexSet()) + "\t"
            + this.getIndividuals().equals(p.getIndividuals()) + "\t"
            + this.areSameIndividuals(p)
            + this.graph.vertexSet() + "\t" + p.graph.vertexSet());
            for (Individual i : this.graph.vertexSet()) {
            System.out.println(i + "\t" + p.containsIndividual(i));
            }
            System.out.println("Edge  :" + this.graph.edgeSet().equals(p.graph.edgeSet()));
             * 
             */
            /*if (!this.areSameIndividuals(p)) {
            return false;
            }
             * 
             */
            //this.graph.v
            if (!this.graph.vertexSet().equals(p.graph.vertexSet())) {
                return false;
            }
            return (this.graph.edgeSet().equals(p.graph.edgeSet()));
        }
        return false;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 61 * hash + (this.graph != null ? this.graph.hashCode() : 0);
        return hash;
    }

    public Individual mostConnectedIndividual() {
        Individual maxIndividual = null, i = null;
        int max = 0;
        Iterator<Individual> iter = this.getIndividualsIterator();
        while (iter.hasNext()) {
            i = iter.next();
            if (this.getNumberOfNeighbours(i) > max) {
                max = this.getNumberOfNeighbours(i);
                maxIndividual = i;
            }
        }
        return maxIndividual;
    }

    private int getNumberOfNeighbours(Individual i) {
        return (this.graph.inDegreeOf(i) + this.graph.outDegreeOf(i));
    }

    boolean containsSubPedigree(Pedigree trueP) {
        boolean contains = true;
        for (Iterator<Relationship> r_iterator = trueP.getRelationshipIterator();
                r_iterator.hasNext();) {
            // mulitple all the p-c relationships in the pedigrees together; create new pedigree; 
            // delete these pedigrees from the big pedigree set (as long as nothing else is using it); add new pedigree instead
            Relationship r = (Relationship) r_iterator.next();
            if (!this.containsRelationship(r)) {
                //System.out.println(r + "\t" + trueP.getIndividuals() + "\t" + this.getIndividuals());
                contains = false;
                break;
            }
        }
        return contains;
    }

    int numberOfRelationshipsOfSubPedigreeContained(Pedigree trueP) {
        int contains = 0;
        for (Iterator<Relationship> r_iterator = trueP.getRelationshipIterator();
                r_iterator.hasNext();) {
            // mulitple all the p-c relationships in the pedigrees together; create new pedigree; 
            // delete these pedigrees from the big pedigree set (as long as nothing else is using it); add new pedigree instead
            Relationship r = (Relationship) r_iterator.next();
            if (this.containsRelationship(r)) {
                //System.out.println(r + "\t" + trueP.getIndividuals() + "\t" + this.getIndividuals());
                contains += 1;
            }
        }
        return contains;
    }

    int numberOfIndividualsOfSubPedigreeContained(Pedigree trueP) {
        int contains = 0;
        for (Iterator<Individual> i_iterator = trueP.getIndividualsIterator();
                i_iterator.hasNext();) {
            // mulitple all the p-c relationships in the pedigrees together; create new pedigree; 
            // delete these pedigrees from the big pedigree set (as long as nothing else is using it); add new pedigree instead
            Individual i = (Individual) i_iterator.next();
            if (this.containsIndividual(i)) {
                //System.out.println(r + "\t" + trueP.getIndividuals() + "\t" + this.getIndividuals());
                contains += 1;
            }
        }
        return contains;
    }

    boolean containsRelationship(Individual i1, Individual i2) {
        return (graph.containsEdge(i2, i1) || graph.containsEdge(i1, i2));
    }

    public ArrayList<Individual> getParents(Individual i) {
        ArrayList<Individual> parents = new ArrayList<Individual>();
        try {
            for (Iterator<Relationship> r_iterator =
                    this.getIncomingRelationships(i); r_iterator.hasNext();) {
                ProbabilisticRelationship r =
                        (ProbabilisticRelationship) r_iterator.next();
                // TODO: might be wrong; might just need ot add one fo them
                if (r.relationshipTypeEquals("PARENT_CHILD")) {
                    //if (r.individual1.equals(i)) {
                    //    parents.add(r.individual2);
                    //} else {
                    parents.add(r.individual1);
                    //}
                }

            }
        } catch (Exception ex) {
            Logger.getLogger(Pedigree.class.getName()).log(Level.SEVERE, null, ex);
        }
        return parents;
    }

    void addConnectingRelationship(Individual i1, Individual i2, boolean VERBOSE)
            throws
            Exception {
        /* get relaitonship from {relationships}
         * look at distance of relationship and add appropriate rel. type.
         * add edge to pedigree
         */
        String index1 = i1.id + "-" + i2.id;
        //System.out.println("To add link from " + i1 + " to " + i2);
        //System.out.println(this.getIndividuals());
        //System.out.println(this.printGraph());

        // todo: need to figure out hwo to deal with adding additional relationships to missing ppl
        ProbabilisticRelationship r_temp = null;
        for (RelationshipTypes rt : PedigreeReconstruction.knownPairwiseDistributions.keySet()) {
            index1 =
                    PedigreeReconstruction.createOrderedKeyWithRelationship(i1, i2, rt);
            r_temp = PedigreeReconstruction.relationships.get(index1);
            if (r_temp != null) {
                break;
            }
        }
        if (r_temp == null) {
            // no relationship found between these two individuals; going to ignore it
            // as in no relationship present in initial set of pairs
            //throw new Exception("No sharing found between " + i1 + " and " + i2);
            if (!i1.isMissing() && !i2.isMissing()) {
                System.out.println("No sharing found between " + i1 + " and " + i2);
            }
            return;
            /*r_temp =
            new ProbabilisticRelationship(i1, i2, -1, -1, PedigreeReconstruction.unrelated);
             * 
             */
        }
        if (VERBOSE) {
            System.out.println("To add link from " + i1 + " to " + i2 + " ----- " + r_temp);
        }
        //System.out.println(index1 + ' ' + index2);
        //System.out.println("r_temp: " + r_temp);

        // if same 2 parents; add as siblings
        boolean sibs = true;
        int numCommonParents = 0;
        Individual commonParent = null;
        if (this.getParents(i1).isEmpty() || this.getParents(i2).isEmpty()) {
            sibs = false;
        } else if (this.getParents(i1).size() > this.getParents(i2).size()) {
            for (Individual parent : this.getParents(i2)) {
                boolean same_parent = false;
                for (Individual parent2 : this.getParents(i1)) {
                    if (parent.missingEquals(parent2)) {
                        same_parent = true;
                        numCommonParents++;
                        commonParent = parent;
                        break;
                    }
                }
                if (!same_parent) {
                    sibs = false;
                }
            }
        } else {
            for (Individual parent : this.getParents(i1)) {
                boolean same_parent = false;
                for (Individual parent2 : this.getParents(i2)) {
                    if (parent.missingEquals(parent2)) {
                        same_parent = true;
                        numCommonParents++;
                        commonParent = parent;
                        break;
                    }
                }
                if (!same_parent) {
                    sibs = false;
                }
                /*System.out.println(parent + "\t" + this.getParents(i2) + " "
                + this.getParents(i1) + " "
                + this.getParents(i2).contains(parent) + " " + sibs);
                if (!this.getParents(i2).contains(parent)) {
                sibs = false;
                }
                 * 
                 */
            }
        }
        if (sibs) {
            //System.out.println("Sibs: " + i1 + "\t" + i2);
            //System.out.println(this.printGraph());
            ProbabilisticRelationship r =
                    new ProbabilisticRelationship(r_temp, PedigreeReconstruction.siblings);

            r.setRelationshipDistribution(
                    PedigreeReconstruction.knownPairwiseDistributions.get(PedigreeReconstruction.siblings));
            // should never be going into this
            if (this.containsRelationship(r)) {
                throw (new Exception("Erroneous execution path"));
            }
            r.calculateRelationshipLikelihood();
            if (VERBOSE) {
                System.out.println("Adding new sibling relationship: " + r.toVerboseString());
            }
            this.addRelationship(r, r.isRelationshipSymmetric());
            //graph.addEdge(i1, i2, r);
            return;
        }


        // if they share 1 parent
        // either half sibs
        // or 1 parent missing and we replace it
        Individual nonMissingParent = null, childWithMissingParent = null, missingParent =
                null;
        if (numCommonParents == 1) {
            // if both parents are not missing, then they are half sibs
            int numMissing = 0;
            for (Individual p : this.getParents(i1)) {
                if (p.isMissing()) {
                    numMissing++;
                    childWithMissingParent = i1;
                    missingParent = p;
                } else if (!p.equals(commonParent)) {
                    nonMissingParent = p;
                }
            }
            for (Individual p : this.getParents(i2)) {
                if (p.isMissing()) {
                    numMissing++;
                    childWithMissingParent = i2;
                    missingParent = p;
                } else if (!p.equals(commonParent)) {
                    nonMissingParent = p;
                }
            }
            if (numMissing == 0) {
                ProbabilisticRelationship r =
                        new ProbabilisticRelationship(r_temp, PedigreeReconstruction.halfsiblings);
                r.setRelationshipDistribution(
                        PedigreeReconstruction.knownPairwiseDistributions.get(PedigreeReconstruction.halfsiblings));
                // should never be going into this
                if (this.containsRelationship(r)) {
                    throw (new Exception("Erroneous execution path"));
                }
                r.calculateRelationshipLikelihood();
                if (VERBOSE) {
                    System.out.println("Adding new sibling relationship: " + r.toVerboseString());
                }
                this.addRelationship(r, r.isRelationshipSymmetric());
                return;
            } else if (numMissing == 1) {
                // either they're half siblings with the other parent missing
                // TODO: ignoring this case for now ^^
                // or they're actually siblings and we replace the missing parent
                // add sibs relationship
                ProbabilisticRelationship r_parent = null;
                String index =
                        PedigreeReconstruction.createOrderedKeyWithRelationship(
                        nonMissingParent, childWithMissingParent, PedigreeReconstruction.parent_child);
                r_parent = PedigreeReconstruction.relationships.get(index);
                if (r_parent != null) {
                    ProbabilisticRelationship r = null;
                    if (r_parent.individual1.equals(nonMissingParent)) {
                        r =
                                new ProbabilisticRelationship(r_parent, PedigreeReconstruction.parent_child);
                    } else {
                        r =
                                new ProbabilisticRelationship(r_parent.individual2, r_parent.individual1,
                                r_parent.totalSharedIBD, r_parent.numberOfSharedSegments,
                                PedigreeReconstruction.parent_child);
                    }
                    r.setRelationshipDistribution(
                            PedigreeReconstruction.knownPairwiseDistributions.get(PedigreeReconstruction.parent_child));
                    if (!this.containsRelationship(r)) {
                        r.calculateRelationshipLikelihood();
                        if (VERBOSE) {
                            System.out.println("Also adding new parent-child relationship: " + r.toVerboseString());
                        }
                        this.addRelationship(r, r.isRelationshipSymmetric());
                        // remove the earlier missing parent relationship
                        Relationship r_missingparent = null;
                        r_missingparent =
                                this.getRelationship(missingParent, childWithMissingParent);
                        /*PedigreeReconstruction.relationships.get(
                        PedigreeReconstruction.createOrderedKeyWithRelationship(
                        missingParent, childWithMissingParent,
                        PedigreeReconstruction.parent_child));
                         * 
                         */
                        if (VERBOSE) {
                            System.out.println("exta missing relationship: " + r_missingparent);
                            System.out.println(missingParent + " " + childWithMissingParent);
                        }
                        if (this.containsRelationship(r_missingparent)) {
                            this.removeRelationship(r_missingparent);
                        }


                        r =
                                new ProbabilisticRelationship(r_temp, PedigreeReconstruction.siblings);
                        r.setRelationshipDistribution(
                                PedigreeReconstruction.knownPairwiseDistributions.get(PedigreeReconstruction.siblings));
                        // should never be going into this
                        if (this.containsRelationship(r)) {
                            throw (new Exception("Erroneous execution path"));
                        }
                        r.calculateRelationshipLikelihood();
                        if (VERBOSE) {
                            System.out.println("Adding new sibling relationship: " + r.toVerboseString());
                        }
                        this.addRelationship(r, r.isRelationshipSymmetric());

                        // also add the parental relationship
                        if (nonMissingParent == null) {
                            throw new Exception("Non missing parent is null - should never happen");
                        }

                        return;
                    } else {
                        //going to be an inconsistent graph
                        if (VERBOSE) {
                            System.out.println("Not going to add extra siblings/p-c relationships");
                        }
                        //System.out.println("Adding multiple relationships between people. Should result in an inconsistent graph");
                        /*throw new Exception("Erroneous execution path. Relationship "
                        + r + " already exists in pedigree\n" + this.printGraph() + "\n"
                        + i1 + " " + this.getParents(i1) + " "
                        + i2 + " " + this.getParents(i2));
                         * 
                         */
                    }
                } else {
                    /*throw new Exception("Parent-child should definitely be quite likely, no?\n"
                    + i1 + " " + this.getParents(i1) + " "
                    + i2 + " " + this.getParents(i2) + " potential parent: "
                    + nonMissingParent + " potential child: " + childWithMissingParent);
                     * 
                     */
                    if (VERBOSE) {
                        System.out.println("Potential parent: " + nonMissingParent
                                + " potential child: " + childWithMissingParent
                                + " are not that likely as parent-child");
                    }
                }
            } else if (numMissing == 2) {// both share one parent and the other one is missing
                if (this.getParents(i1).contains(commonParent)
                        && this.getParents(i2).contains(commonParent)) {
                    ProbabilisticRelationship r =
                            new ProbabilisticRelationship(r_temp, PedigreeReconstruction.siblings);
                    r.setRelationshipDistribution(
                            PedigreeReconstruction.knownPairwiseDistributions.get(PedigreeReconstruction.siblings));
                    // should never be going into this
                    if (this.containsRelationship(r)) {
                        throw (new Exception("Erroneous execution path"));
                    }
                    r.calculateRelationshipLikelihood();
                    if (VERBOSE) {
                        System.out.println("Adding new sibling relationship: " + r.toVerboseString());
                    }
                    this.addRelationship(r, r.isRelationshipSymmetric());
                } else {
                    if (VERBOSE) {
                        System.out.println("How are they siblings if they don't share a parent?");
                        System.out.println("parent: " + nonMissingParent + " sibs: "
                                + i1 + "(" + this.getParents(i1) + ") "
                                + i2 + "(" + this.getParents(i2) + ") ");
                    }
                }
            } else {
                if (VERBOSE) {
                    System.out.println("Weird?");
                }
            }
        }

        List<Relationship> path =
                DijkstraShortestPath.findPathBetween(this.convertToPathGraph(), i2, i1);
        //System.out.println("Path");
        //System.out.println(this.printGraph());
        //System.out.println(this.convertToPathGraph());
        if (path == null) {
            path =
                    DijkstraShortestPath.findPathBetween(this.convertToPathGraph(), i1, i2);
        }
        if (VERBOSE) {
            System.out.println("Path: " + path);
        }

        // work out relationships between i1 and i2
        // TODO: is this correct?
        int founders = 0;
        int meioses_up = 0, meioses_down = 0;
        boolean all_sibs = true, all_but_first_sibs = true;


        //boolean all_parent = true;
        if (path == null) {
            ProbabilisticRelationship r =
                    new ProbabilisticRelationship(r_temp, PedigreeReconstruction.unrelated);
            r.setRelationshipDistribution(
                    PedigreeReconstruction.knownPairwiseDistributions.get(r.relationshipType));
            /* think this isnt necessary
            if (this.containsRelationship(r)) {
            throw (new Exception("Erroneous execution path : " + r + "\n" + this.printGraph()));
            }
             * 
             */
            r.calculateRelationshipLikelihood();
            if (VERBOSE) {
                System.out.println("Adding new unrelated relationship: " + r.toVerboseString());
            }
            this.addRelationship(r, r.isRelationshipSymmetric());
            graph.addEdge(i1, i2, r);
            return;
        } else {
            // TODO: need to make this clearer
            for (Relationship r_path : path) {
                // TODO: is this correct?
                /*if (!r_path.relationshipType.equals(PedigreeReconstruction.parent_child)) {
                all_parent = false;
                }
                 * 
                 */
                if (!r_path.relationshipType.equals(PedigreeReconstruction.parent_child)
                        && !r_path.relationshipType.equals(PedigreeReconstruction.siblings)
                        && !r_path.relationshipType.equals(PedigreeReconstruction.unrelated)
                        && !r_path.relationshipType.equals(PedigreeReconstruction.grandparent_grandchild)
                        && !r_path.relationshipType.equals(PedigreeReconstruction.avuncular)) {
                    if (VERBOSE) {
                        System.out.println("Not adding relationship: " + path);
                    }
                    return;
                }
                /* if (r_path.relationshipType.meiosesUp == r_path.relationshipType.meiosesDown) {
                // not quite accurate - need to also ensure that the relationship is more levels
                //if (r_path.relationshipType.equals(PedigreeReconstruction.siblings)) {
                // include first one
                /*if (meioses_up == 0) {
                meioses_up = PedigreeReconstruction.siblings.meiosesUp;
                meioses_down =
                PedigreeReconstruction.siblings.meiosesDown;
                founders = PedigreeReconstruction.siblings.founders;
                }
                 * 
                
                continue;
                }
                 * 
                 */
                if (r_path.relationshipType.meiosesUp == Integer.MAX_VALUE) {
                    meioses_up = Integer.MAX_VALUE;
                } else {
                    meioses_up += r_path.relationshipType.meiosesUp;
                }
                if (r_path.relationshipType.meiosesDown == Integer.MAX_VALUE) {
                    meioses_down = Integer.MAX_VALUE;
                } else {
                    meioses_down += r_path.relationshipType.meiosesDown;
                }
                if (r_path.relationshipType.founders > 1) {
                    founders = r_path.relationshipType.founders;
                } else {
                    founders = 1;
                }
                if (!r_path.relationshipType.equals(PedigreeReconstruction.siblings)) {
                    all_sibs = false;
                }
                if (r_path != path.get(0)
                        && !r_path.relationshipType.equals(PedigreeReconstruction.siblings)) {
                    all_but_first_sibs = false;
                }

            }
            if (meioses_up == 0) {
                meioses_up = path.get(0).relationshipType.meiosesUp;
                meioses_down = path.get(0).relationshipType.meiosesDown;
                founders = path.get(0).relationshipType.founders;
            }
        }
        // actually going to ignore unrelateds - also ignoring it further up
        ProbabilisticRelationship r = null;
        if (meioses_up == Integer.MAX_VALUE || meioses_down == Integer.MAX_VALUE) {
            r =
                    new ProbabilisticRelationship(r_temp, PedigreeReconstruction.unrelated);
        } else if (all_sibs) {
            r =
                    new ProbabilisticRelationship(r_temp, PedigreeReconstruction.siblings);
        } else if (all_but_first_sibs) {
            if (VERBOSE) {
                System.out.println("All relationships in path are sibs (except for first one)");
            }
            if (path.get(0).individual1.equals(r_temp.individual1)) {
                r =
                        new ProbabilisticRelationship(r_temp, path.get(0).relationshipType);
            } else {
                r =
                        new ProbabilisticRelationship(r_temp.individual2, r_temp.individual1,
                        r_temp.totalSharedIBD, r_temp.numberOfSharedSegments,
                        path.get(0).relationshipType);
                if (VERBOSE) {
                    System.out.println("Reversing order of relationship");
                }
            }
            // remove missing parents
            //System.out.println(this.getParents(r.individual1) + " " + this.getParents(r.individual2));
            // because it doesn't yet have the second one added.
            if (this.getParents(r.individual2).size() > 1) {
                for (Individual p : this.getParents(r.individual2)) {
                    //System.out.println("Parent: " + p);
                    if (p.isMissing() && this.getParents(r.individual2).size() > 1) {
                        if (VERBOSE) {
                            System.out.println("Removing missing parent " + p);
                        }
                        Relationship r_missingparent =
                                this.getRelationship(p, r.individual2);
                        /*PedigreeReconstruction.relationships.get(
                        PedigreeReconstruction.createOrderedKeyWithRelationship(
                        p, r.individual1,
                        PedigreeReconstruction.parent_child));*/
                        if (this.containsRelationship(r_missingparent)) {
                            this.removeRelationship(r_missingparent);
                        }
                    }
                }
            }
        } /*else if (!new RelationshipTypes(meioses_up, meioses_down, founders).equals(PedigreeReconstruction.parent_child)
        && !new RelationshipTypes(meioses_up, meioses_down, founders).equals(PedigreeReconstruction.siblings)
        && !new RelationshipTypes(meioses_up, meioses_down, founders).equals(PedigreeReconstruction.unrelated)
        && !new RelationshipTypes(meioses_up, meioses_down, founders).equals(PedigreeReconstruction.grandparent_grandchild)
        && !new RelationshipTypes(meioses_up, meioses_down, founders).equals(PedigreeReconstruction.avuncular)) {
        System.out.println("Ignoring higher order relationships for now");
        return;
        } 
         */ else {
            /*
            System.out.println(path.get(0).individual1.equals(i1) + " "
            + path.get(0).individual1 + " "
            + i1 + " "
            + r_temp);
             * 
             */
            // if the first one is 'higher' in the pedigree
            if (path.get(0).individual1.equals(r_temp.individual1)) {
                r =
                        new ProbabilisticRelationship(r_temp,
                        new RelationshipTypes(meioses_up, meioses_down, founders));
            } else {
                r =
                        new ProbabilisticRelationship(r_temp.individual2, r_temp.individual1,
                        r_temp.totalSharedIBD, r_temp.numberOfSharedSegments,
                        new RelationshipTypes(meioses_up, meioses_down, founders));
                //System.out.println("Reversing order of relationship");
            }
        }

        if (PedigreeReconstruction.knownPairwiseDistributions.get(r.relationshipType) == null) {
            ArrayList<Double> lengthPdf = new ArrayList<Double>();
            lengthPdf.add(r.totalSharedIBD);
            //double[] countPdf = {r.numberOfSharedSegments};
            ArrayList<Double> countPdf = new ArrayList<Double>();
            countPdf.add((double) r.numberOfSharedSegments);
            // adding actual data in another function, later on
            RelationshipDistribution dist =
                    new RelationshipDistribution("", false, lengthPdf, countPdf);
            PedigreeReconstruction.knownPairwiseDistributions.put(r.relationshipType, dist);
            //PedigreeReconstruction.knownPairwiseDistributions.put(r.relationshipType, null);
            // TODO: might not need that update distributions; actually, i will but in a different wat
            // TODO: introduce proper names
        }

        r.setRelationshipDistribution(
                PedigreeReconstruction.knownPairwiseDistributions.get(r.relationshipType));
        // if it's unrelated; we're basically just replacing it with unrelated
        if (this.containsRelationship(r)) {
            // not replacing an unrelated with another unrelated
            if (this.getRelationship(r.individual1, r.individual2).relationshipType.equals(
                    PedigreeReconstruction.unrelated)) {
                return;
            }
            if (VERBOSE) {
                System.out.println("Relationship already exists: " + r.toVerboseString());
            }
            return;
            // TODO: figure it out!
            //throw (new Exception("Erroneous execution path : " + r + "\n" + this.printGraph()));
        }
        /*
        if (all_parent) {
        System.out.println("All parent: ");
        System.out.println("Path: " + path); // + "\n" + this.printGraph());
        System.out.println("new relationship: " + r + "\t" + this.getNumberOfIndividuals() + "\n----\n");
        }
         * 
         */

        /*+ " "
         * 
        + this.containsRelationship(i1, i2) + " "
        + this.containsRelationship(i2, i1) + this.containsRelationship(r) + "\n----\n
         * 
         */
        // add relationship to graph
        // TODO: work out the directions

        r.calculateRelationshipLikelihood();
        if (VERBOSE) {
            System.out.println("Adding new relationship: " + r.toVerboseString());
        }
        this.addRelationship(r, r.isRelationshipSymmetric());
        //graph.addEdge(i1, i2, r); // todo: shouldnt need this line
        //throw new UnsupportedOperationException("Not yet implemented");
    }

    private int distance(Individual i1, Individual i2) {
        // TODO: has to be a path that's only parent-child
        List<Relationship> path =
                DijkstraShortestPath.findPathBetween(graph, i2, i1);
        System.out.println(path);
        return path.size();
    }

    int getNumberOfRelationships(Individual i) {
        if (graph.containsVertex(i)) {
            return this.graph.edgesOf(i).size();
        } else {
            return 0;
        }
    }

    public ArrayList<Individual> getFounders() {
        ArrayList<Individual> founders = new ArrayList<Individual>();
        for (Individual i : this.getIndividuals()) {
            if (this.getParents(i).isEmpty()) {
                founders.add(i);
            }
        }
        return founders;
    }

    Iterable<Individual> getChildren(Individual i) {
        ArrayList<Individual> children = new ArrayList<Individual>();
        try {
            for (Iterator<Relationship> r_iterator =
                    this.getIncomingRelationships(i); r_iterator.hasNext();) {
                ProbabilisticRelationship r =
                        (ProbabilisticRelationship) r_iterator.next();
                if (r.relationshipTypeEquals("PARENT_CHILD")) {
                    children.add(r.individual2);
                }

            }
        } catch (Exception ex) {
            Logger.getLogger(Pedigree.class.getName()).log(Level.SEVERE, null, ex);
        }
        return children;
    }

    int getNumberOfNonMissingIndividuals() {
        int number = 0;
        for (Individual i : this.getIndividuals()) {
            if (!i.isMissing()) {
                number++;
            }
        }
        return number;
    }

    private boolean areSameIndividuals(Pedigree p) {
        for (Individual i : p.getIndividuals()) {
            if (!this.containsIndividual(i)) {
                return false;
            }
        }
        return true;
    }

    private DirectedGraph<Individual, Relationship> convertToPathGraph() {
        DirectedGraph<Individual, Relationship> pathgraph =
                new DefaultDirectedGraph<Individual, Relationship>(
                Relationship.class);
        for (Relationship r : this.getRelationships()) {
            if (r.relationshipType.equals(PedigreeReconstruction.parent_child) || r.relationshipType.equals(PedigreeReconstruction.siblings)) {
                if (!pathgraph.containsVertex(r.individual1)) {
                    pathgraph.addVertex(r.individual1);
                }
                if (!pathgraph.containsVertex(r.individual2)) {
                    pathgraph.addVertex(r.individual2);
                }

                pathgraph.addEdge(r.individual1, r.individual2, r);
            }
        }
        return pathgraph;
    }
}
